﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace Classes
{
    public class GraphImpl : IGraphImpl
    {
        IList<City> cities;
        IList<Flight> flights;
        IList<City> citiesOfSpan;
        IList<Flight> flightsForSearch;
        IList<City> sequenceOfParentCities;
        IList<Flight> listOfFlights;
        IList<Flight> sequence;
        IList<City> theCitiesList;
        ListOfConnections listOfConnections;
        IList<City> citiesInConnection;
        IList<Flight> flightsInConnection;
        IList<City> queue;
        IList<City> path;
        





        public GraphImpl()
        {
            citiesOfSpan = new List<City>();
            flights = new List<Flight>();
            cities = new List<City>();
            flightsForSearch = new List<Flight>();
            sequenceOfParentCities = new List<City>();
            listOfFlights = new List<Flight>();
            sequence = new List<Flight>(); // GetARoutetoTheCity(City from, City to)
            theCitiesList = new List<City>(); //  public IList<City> GetCities(City city)
            citiesInConnection = new List<City>();
            flightsInConnection = new List<Flight>();
            listOfConnections = new ListOfConnections();
            queue = new List<City>();

        }

        public void AddCity(City c)
        {
            cities.Add(c);
        }


        public void AddFlight(City from, City to, int noOfSeats, int id, DateTime dt, DateTime arrival, int price, string company)
        {	//PRE (to,from) is not contained in the graph

            from.AddFlight(to, noOfSeats, id, dt, arrival, price, company);
        }


        public IList<City> GetAdjacencies(City city)
        {
            IList<City> list = new List<City>();

            foreach (Flight f in city.Flights())
            {

                list.Add(f.To);
            }

            return list;

        }



        public ListOfConnections SearchForConnections(City weGoFrom, City weGoTo)
        {


            IList<City> visited = new List<City>();
            visited.Add(weGoFrom);


            ListOfConnections listOfConnections = DepthFirst(visited, weGoFrom, weGoTo); //used to return this variable I changet it like this for testing
            return listOfConnections;
        }


        public ListOfConnections DepthFirst(IList<City> visited, City fr, City to)
        {

            IList<City> adj = GetAdjacencies(visited.Last());

            foreach (City c in adj)
            {

                if (visited.Contains(c))
                {

                    continue;
                }
                if (c.Equals(to))
                {

                    visited.Add(c);

                    Connection connection = new Connection();
                    connection.MakeFlightsOfCities(visited);
                    DateTime old = DateTime.Now;
                    bool allow = false;
                    foreach (Flight item in connection.FlightList())
                    {
                        if (old < item.DepartureTime)
                        {

                            allow = true;
                            old = item.ArrivalTime;
                        }
                        else
                        {
                            allow = false;
                            Console.WriteLine("fail");

                            break;
                        }
                    }
                    if (allow)
                        listOfConnections.AddConnection(connection);




                    City f = visited.Last();
                    visited.Remove(f);
                    // break; now it does actually work work
                }

                foreach (City x in adj)
                    if (visited.Contains(x) || x.Equals(to))
                    {

                        continue;
                    }
                if (visited.Last() != to)
                {

                    visited.Last().Flights().First(x => x.To == c && x.Used != true).Used = true;
                    if (visited.Last().Flights().FindAll(x => x.To == c).Count() == visited.Last().Flights().FindAll(x => x.To == c && x.Used == true).Count())
                    {
                        List<Flight> theList = visited.Last().Flights().FindAll(x => x.To == c);
                        foreach (Flight f in theList)
                        {
                            f.Used = false;
                        }

                    }
                }

                visited.Add(c);
                DepthFirst(visited, fr, to);
                City last = visited.Last();
                visited.Remove(last);
            }

            return listOfConnections;
        }

        public Connection DijsktraInvoker(City startCity, City destinationCity) //cost efficient
        {
            Dijkstra(startCity);
            destinationCity.AddLastCityToMakePath();


            Connection connection = new Connection();


            connection.MakeFlightsOfCities(destinationCity.Path);

            
            return connection;
        }

        private City GetNextVertex()
        {
            int min = int.MaxValue;
            City sampleCity = new City();

            /* Search through queue to find the next node having the smallest distance */
            foreach (City c in queue)
            {
                if (c.PriceToGetHere <= min)
                {
                    min = c.PriceToGetHere;
                    //if(!c.Mark)
                    sampleCity = c;
                }
            }
            queue.Remove(sampleCity);
            return sampleCity;

        }
        public void Dijkstra(City startCity)//cost eff
        {
            
                foreach (City c in cities)
                {
                    queue.Add(c);
                }
                startCity.PriceToGetHere = 0;


                while (queue.Count > 0)
                {
                    path = new List<City>();
                    City u = GetNextVertex();
              


                    foreach (City v in GetAdjacencies(u))
                    {
                        foreach (Flight f in u.Flights().FindAll(x => x.To == v))
                        {
                            if (v.LastHour > f.ArrivalTime)
                            {
                                if (v.PriceToGetHere > u.PriceToGetHere + f.PriceOfTheOneSeat)
                                {
                                    v.PriceToGetHere = u.PriceToGetHere + f.PriceOfTheOneSeat;
                                    v.AddCityToPath(u);
                                    v.LastHour = f.ArrivalTime;
                                }
                            }
                            else
                            {
                                RerunDijkstra(startCity, f, u, v);
                            }
                        }
                    }
                }
            }
        public void RerunDijkstra(City startCity, Flight f, City u, City v)
        {

            foreach (Flight g in u.Flights().FindAll(x => x.To == v))
            {
                if(g.ArrivalTime > v.LastHour)
                {
                    u.Flights().Remove(g);
                }
            }
            v.LastHour = DateTime.Today;
            Dijkstra(startCity);
        }
        public void DijkstraTime(City startCity)
        {
            
            
            foreach (City c in cities)
            {
                queue.Add(c); 
                
            }

           

            startCity.TimeToGetHere = new TimeSpan(0, 0, 0, 0, 0);


            while (queue.Count > 0)
            {
                path = new List<City>();
                City u = GetNextVertexTime();
                DateTime lastHour = DateTime.Now;
                foreach (City v in GetAdjacencies(u))
                {

                   // lastHour && x.DepartureTime > lastHour

                    foreach (Flight f in u.Flights().FindAll(x => x.To == v))
                    {

                       
                            if (v.TimeToGetHere > u.TimeToGetHere + f.getDuration)
                            {
                                v.TimeToGetHere = u.TimeToGetHere + f.getDuration;
                                v.AddCityToPath(u);
                            }
                    }
                }
            }
        }
        private City GetNextVertexTime()
        {
            TimeSpan min = TimeSpan.MaxValue;
            City sampleCity = new City();
            //int a;
            /* Search through queue to find the next node having the smallest distance */
            foreach (City c in queue)
            {
                //a = TimeSpan.Compare(c.TimeToGetHere, min);
                if (c.TimeToGetHere <= min)
                {
                    min = c.TimeToGetHere;
                    //if(!c.Mark)
                    sampleCity = c;
                }
            }
            queue.Remove(sampleCity);
            return sampleCity;

        }



        public Connection DijsktraInvokerTime(City startCity, City destinationCity)
        {
            DijkstraTime(startCity);
            destinationCity.AddLastCityToMakePath();
            Connection connection = new Connection();
            connection.MakeFlightsOfCities(destinationCity.Path);
            return connection;
        }


    }

}
